/*
** $Id: lcorolib.c $
** Coroutine Library 协程库
** See Copyright Notice in lua.h
*/

/* 定义文件标识和库标志 */
#define lcorolib_c
#define LUA_LIB

/* 包含必要的头文件 */
#include "lprefix.h"


#include <stdlib.h>

#include "lua.h"

#include "lauxlib.h"
#include "lualib.h"


/*
** 获取协程
** L: 主 Lua 状态机;
** 参数 1: 协程对象;
** 返回值: 协程的 lua_State 指针。
** 检查参数 1 是否为协程，并返回该协程的 lua_State 指针。
*/
static lua_State *getco (lua_State *L) {
  lua_State *co = lua_tothread(L, 1); /* 将参数转换为协程状态机 */
  luaL_argexpected(L, co, 1, "thread"); /* 检查转换结果，确保参数 1 是协程 */
  return co; /* 返回协程的状态机指针 */
}

/*
** 恢复一个协程的执行。在非错误情况下返回结果的数量，错误时返回-1。
**
** @param L 主 Lua 状态机。
** @param co 要恢复的协程的 Lua 状态机。
** @param narg 要传递给协程的参数数量。
** @return 如果成功，返回协程返回的结果数量；如果发生错误，返回-1。
*/
static int auxresume (lua_State *L, lua_State *co, int narg) {
  int status, nres;
  // 检查协程的栈空间是否足够放下narg个参数，不足则报错。
  if (l_unlikely(!lua_checkstack(co, narg))) {
    lua_pushliteral(L, "恢复时参数过多too many arguments to resume");
    return -1;  /* 错误标志 */
  }
  // 将参数从主状态机移动到协程状态机中。
  lua_xmove(L, co, narg);
  // 恢复协程的执行，并指定传入的参数数量，获取执行结果状态和结果数量。
  status = lua_resume(co, L, narg, &nres);
  // 如果协程正常执行或暂停，检查主状态机的栈空间是否足够放下结果。
  if (l_likely(status == LUA_OK || status == LUA_YIELD)) {
    if (l_unlikely(!lua_checkstack(L, nres + 1))) {
      lua_pop(co, nres);  /* 移除结果，因栈空间不足 */
      lua_pushliteral(L, "结果过多无法继续恢复too many results to resume");
      return -1;  /* 错误标志 */
    }
    // 将协程执行结果移动到主状态机中。
    lua_xmove(co, L, nres);  
    return nres;  /* 返回结果数量 */
  }
  else {
    // 如果协程执行出错，将错误消息移动到主状态机中。
    lua_xmove(co, L, 1);  
    return -1;  /* 错误标志 */
  }
}


/*
 * 继续执行一个协程
 * 参数：
 *   L：主 Lua 状态机
 * 返回值：
 *   如果协程成功恢复执行，则返回 true + 协程返回的值的数量；
 *   如果协程恢复失败，则返回 false + 错误消息。
 */
static int luaB_coresume (lua_State *L) {
  lua_State *co = getco(L);  // 获取当前栈顶的协程
  int r;
  r = auxresume(L, co, lua_gettop(L) - 1);  // 尝试恢复协程执行，参数包括主状态机、协程状态机和传递给协程的参数数量
  if (l_unlikely(r < 0)) {  // 检查协程恢复是否出错
    lua_pushboolean(L, 0);  // 推入一个 false 表示恢复失败
    lua_insert(L, -2);  // 将错误消息（已经在栈顶）移动到 false 之下
    return 2;  /* 返回 false 和错误消息 */
  }
  else {
    lua_pushboolean(L, 1);  // 推入一个 true 表示恢复成功
    lua_insert(L, -(r + 1));  // 将 true 移动到协程返回值之上
    return r + 1;  /* 返回 true 和协程返回的值的数量 */
  }
}


/*
 * 用于辅助恢复协程执行的函数。
 * 
 * @param L 主 Lua 状态机。
 * @return 如果协程正常运行，则返回恢复的值；如果发生错误，则触发 Lua 错误。
 */
static int luaB_auxwrap (lua_State *L) {
  /* 获取协程的 Lua 状态机 */
  lua_State *co = lua_tothread(L, lua_upvalueindex(1));
  /* 恢复协程的执行并获取结果 */
  int r = auxresume(L, co, lua_gettop(L));
  if (l_unlikely(r < 0)) {  /* 如果执行出错 */
    int stat = lua_status(co);  /* 获取协程的当前状态 */
    if (stat != LUA_OK && stat != LUA_YIELD) {  /* 如果不是正常状态或挂起状态，表示协程内发生错误 */
      stat = lua_closethread(co, L);  /* 关闭协程，处理未完成的变量 */
      lua_assert(stat != LUA_OK);  /* 确认协程确实存在错误 */
      lua_xmove(co, L, 1);  /* 将错误消息移动到主 Lua 状态机中 */
    }
    /* 处理非内存错误且错误对象为字符串的情况，添加额外错误信息 */
    if (stat != LUA_ERRMEM &&  
        lua_type(L, -1) == LUA_TSTRING) {
      luaL_where(L, 1);
      lua_insert(L, -2);
      lua_concat(L, 2);
    }
    return lua_error(L);  /* 将错误向上传播 */
  }
  return r;  /* 返回协程恢复的值 */
}


/*
 * 创建一个新的协程
 * 参数：
 *   L - Lua状态机，表示当前的Lua环境。
 * 返回值：
 *   返回1，表示函数执行成功。
 */
static int luaB_cocreate (lua_State *L) {
  lua_State *NL; // 创建一个新的Lua线程
  luaL_checktype(L, 1, LUA_TFUNCTION); // 检查参数1是否为函数类型
  NL = lua_newthread(L); // 在当前Lua状态机上创建一个新的线程
  lua_pushvalue(L, 1);  /* 将参数1（函数）移动到当前状态机的栈顶 */
  lua_xmove(L, NL, 1);  /* 将函数从当前状态机（L）移动到新创建的线程（NL）中 */
  return 1;
}


/**
 * 创建一个Lua协程并将其包装成一个Lua函数。
 * 
 * @param L lua_State的指针，表示当前的Lua环境。
 * @return 返回1，表示压入了一个Lua值（即包装后的协程函数）。
 */
static int luaB_cowrap (lua_State *L) {
  luaB_cocreate(L);  // 创建一个新的协程
  lua_pushcclosure(L, luaB_auxwrap, 1);  // 将luaB_auxwrap函数设置为一个闭包，将其与创建的协程关联
  return 1;  // 表示函数执行成功，并返回了1个Lua值
}


/*
 * luaB_yield函数: 向协程发送挂起信号。
 * 参数:
 *   L - Lua状态机指针，表示当前的Lua环境。
 * 返回值:
 *   返回lua_yield函数的返回值，通常为0，具体取决于lua_yield的实现和调用情况。
 */
static int luaB_yield (lua_State *L) {
  // 调用lua_yield函数，挂起当前协程，并返回控制权给调用者。
  return lua_yield(L, lua_gettop(L));
}


/*
 * 定义了四个状态常量，用于表示Cos调度系统的不同状态。
 * 这些状态常量在调度器中被广泛使用，以决定进程或线程的当前状态及下一步行动。
 * 
 * COS_RUN: 表示进程或线程正在执行。
 * COS_DEAD: 表示进程或线程已经结束执行。
 * COS_YIELD: 表示进程或线程主动让出CPU，等待下一次调度。
 * COS_NORM: 表示进程或线程的正常状态，可以被调度执行。
 */
#define COS_RUN		0
#define COS_DEAD	1
#define COS_YIELD	2
#define COS_NORM	3

/* 定义状态名称的静态常量数组 */
static const char *const statname[] =
{"运行中running", "废弃dead", "挂起suspended", "常规normal"};

/*
 * 辅助函数，用于确定给定协程的状态。
 * 
 * 参数：
 * L - 主 Lua 状态机。
 * co - 需要检查状态的协程。
 * 
 * 返回值：
 * 返回一个表示协程状态的整数，具体状态有：
 * COS_RUN - 协程正在运行；
 * COS_YIELD - 协程被挂起；
 * COS_NORM - 协程正常（有堆栈帧且不在初始状态）；
 * COS_DEAD - 协程死亡或出现错误。
 */
static int auxstatus (lua_State *L, lua_State *co) {
  /* 如果 L 和 co 指向同一个 Lua 状态，则认为协程正在运行 */
  if (L == co) return COS_RUN;
  else {
    /* 根据协程的 Lua 状态确定协程的辅助状态 */
    switch (lua_status(co)) {
      case LUA_YIELD: /* 协程被挂起 */
        return COS_YIELD;
      case LUA_OK: { /* 协程执行正常 */
        lua_Debug ar;
        /* 检查协程是否有堆栈帧，有则认为协程正在运行，无则进一步判断 */
        if (lua_getstack(co, 0, &ar))  
          return COS_NORM;  /* 协程正在运行 */
        else if (lua_gettop(co) == 0) /* 协程堆栈为空，认为协程已结束 */
            return COS_DEAD;
        else
          return COS_YIELD;  /* 初始状态下，假定协程被挂起 */
      }
      default:  /* 出现错误，协程死亡 */
        return COS_DEAD;
    }
  }
}


/**
 * 获取协程的状态，并将其以字符串形式压入堆栈。
 * 
 * @param L lua_State的指针，表示当前的Lua环境。
 * @return 返回1，表示压入了一个元素到堆栈。
 */
static int luaB_costatus (lua_State *L) {
  // 获取当前Lua环境关联的协程
  lua_State *co = getco(L);
  // 将协程的状态转换为字符串，并压入堆栈
  lua_pushstring(L, statname[auxstatus(L, co)]);
  return 1;
}


/**
 * 检查指定的协程是否可以被挂起。
 * 
 * @param L lua_State的指针，表示当前的Lua环境。
 * @return 返回1，并在Lua栈上压入一个布尔值，表示指定的协程是否可以被挂起。
 */
static int luaB_yieldable (lua_State *L) {
  // 如果第一个参数是none，使用当前的lua_State；否则，获取第一个参数作为协程。
  lua_State *co = lua_isnone(L, 1) ? L : getco(L);
  // 检查协程是否可以被挂起，并将结果压入Lua栈。
  lua_pushboolean(L, lua_isyieldable(co));
  return 1;
}


/**
 * 查询当前Lua状态机是否为主协程。
 * 
 * @param L 指向当前Lua状态机的指针。
 * @return 返回2，同时栈顶增加两个元素：一个是表示是否为主协程的布尔值（true或false），另一个是表示当前协程的线程对象。
 */
static int luaB_corunning (lua_State *L) {
  // 将当前线程压栈，并返回一个表示是否是主线程的int值（1表示是主线程，0表示不是）
  int ismain = lua_pushthread(L); 
  // 将表示是否为主线程的布尔值（true或false）压栈
  lua_pushboolean(L, ismain);
  // 返回压栈的元素个数
  return 2;
}


/*
 * 用于关闭一个协程的函数。
 * 如果协程已经结束或者可以安全关闭，则关闭协程，并根据情况返回成功或错误信息。
 * 参数：
 *   L - 主 Lua 状态机的指针。
 * 返回值：
 *   如果成功关闭协程，返回 1，并在栈上压入一个真值。
 *   如果关闭失败，返回 2，并将错误消息从协程状态机移动到主状态机的栈上。
 */

static int luaB_close (lua_State *L) {
  lua_State *co = getco(L);  // 获取当前Lua状态机关联的协程。
  int status = auxstatus(L, co);  // 获取协程的状态。
  switch (status) {  // 根据协程的不同状态进行处理。
    case COS_DEAD: case COS_YIELD: {  // 如果协程已经死亡或者处于挂起状态。
      status = lua_closethread(co, L);  // 尝试关闭协程。
      if (status == LUA_OK) {  // 如果关闭成功。
        lua_pushboolean(L, 1);  // 在主状态机的栈上压入真值，表示成功。
        return 1;  // 返回成功标志。
      }
      else {  // 如果关闭失败。
        lua_pushboolean(L, 0);  // 在主状态机的栈上压入假值，表示失败。
        lua_xmove(co, L, 1);  // 将协程状态机上的错误消息移动到主状态机的栈上。
        return 2;  // 返回错误标志。
      }
    }
    default:  // 如果协程是正常运行或其它未处理的状态。
      return luaL_error(L, "cannot close a 无法关闭一个 %s 协程coroutine", statname[status]);  // 抛出错误，不能关闭当前状态的协程。
  }
}

/*
 * 协程功能的注册表
 * 该表包含了所有协程相关函数的英文名和中文名，以及它们对应的实现函数。
 * 该注册表用于在Lua中注册这些协程函数，以便于在Lua脚本中调用。
 */

static const luaL_Reg co_funcs[] = {
   /* 英文版协程函数名 */
   {"create", luaB_cocreate}, /* 创建一个新的协程 */
   {"resume", luaB_coresume}, /* 恢复一个协程的执行 */
   {"running", luaB_corunning}, /* 获取当前正在运行的协程 */
   {"status", luaB_costatus}, /* 获取一个协程的状态 */
   {"wrap", luaB_cowrap}, /* 将一个函数封装成一个协程 */
   {"yield", luaB_yield}, /* 使当前协程挂起 */
   {"isyieldable", luaB_yieldable}, /* 检查当前协程是否可以挂起 */
   {"close", luaB_close}, /* 关闭一个协程 */

   /* 中文版协程函数名 */
   {"创建", luaB_cocreate}, /* 创建一个新的协程 */
   {"恢复", luaB_coresume}, /* 恢复一个协程的执行 */
   {"程名", luaB_corunning}, /* 获取当前正在运行的协程 */
   {"状态", luaB_costatus}, /* 获取一个协程的状态 */
   {"程包", luaB_cowrap}, /* 将一个函数封装成一个协程 */
   {"挂起", luaB_yield}, /* 使当前协程挂起 */
   {"可挂起", luaB_yieldable}, /* 检查当前协程是否可以挂起 */
   {"关闭", luaB_close}, /* 关闭一个协程 */

   {NULL, NULL} /* 注册表结束标志 */
};



/**
 * 在Lua中打开协程库。
 * 
 * @param L Lua状态机指针，表示当前的Lua环境。
 * @return 总是返回1，表示在Lua中创建了一个新的库表。
 */
LUAMOD_API int luaopen_coroutine (lua_State *L) {
  // 创建一个新的库表并将其注册到Lua环境中
  luaL_newlib(L, co_funcs);
  return 1;
}

